﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using System.Globalization;
using Sherwood.SignOn.Server.Models.Repositories;
using Sherwood.Mvc;
using log4net;
using Sherwood.SignOn.Server.Models.Services;
using Sherwood.SignOn.Server.Content;

namespace Sherwood.SignOn.Server.Models.ViewModels
{
    public class RegisterViewModel : ViewModelBase
    {

        private static readonly ILog Log = LogManager.GetLogger(typeof(RegisterViewModel));

        public string UserName { get; set; }

        public string Password { get; set; }

        public string Password2 { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

        public Gender Gender { get; set; }

        public DateTime? DateOfBirth { get; set; }

        public string Email { get; set; }

        public string CultureName1 { get; set; }

        public string CultureName2 { get; set; }

        public string CultureName3 { get; set; }

        public string Country { get; set; }

        public string Mobile { get; set; }

        public string RequestedUrl { get; set; }

        public bool EmailOrPasswordChanged { get; set; }

        public string PendingEmailChangeText { get; set; }

        public string EmailDisplay { get; set; }

        public bool RememberMe { get; set; }

        public bool CreateUserAccount(ModelStateDictionary state)
        {
           return CreateOrEditUserAccount(state, Guid.Empty);
        }

        public bool EditUserAccount(ModelStateDictionary state, Guid userAccountId)
        {
           return CreateOrEditUserAccount(state, userAccountId);
        }



        protected bool CreateOrEditUserAccount(ModelStateDictionary state, Guid userAccountId)
        {
            bool isNewUser = userAccountId.Equals(Guid.Empty);
            bool valid = true;
            IUserAccount account = null;

            Email = Email != null ? Email.ToLower().Trim() : "";
            UserName = UserName != null ? UserName.Trim().ToLower() : "";

            if (Log.IsDebugEnabled)
            {
                Log.Debug("Creating/editing account: " + UserName + " (" + Email + ")");
            }

        
            //Create account
            CreateUserAccountParams createAccountParams = new CreateUserAccountParams
            {
                UserAccountId = userAccountId,
                RequestedUrl = RequestedUrl,
                UserName = UserName,
                DateOfBirth = DateOfBirth,
                Email = Email,
                Gender = Gender,
                CultureName1 = CultureName1,
                CultureName2 = CultureName2,
                CultureName3 = CultureName3,
                Country = Country,
                Mobile = Mobile,
                FirstName = FirstName,
                LastName = LastName,
                Password = Password,
                IsEnabled = true,
                VerificationStatus = isNewUser ? VerificationStatus.Unverified : VerificationStatus.Verified
            };


            //VALIDATE ACCOUNT PARAMETERS:

            //Validate email
            Regex emailRegEx = new Regex("^([0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z][-.\\w]*)*@([0-9a-zA-Z][-\\w]*\\.)+[a-zA-Z]{2,9})$");
            if (Email == "" || !emailRegEx.IsMatch(Email))
            {
                valid = false;
                state.AddModelError("Email", Translations.Register_Email_Invalid);
            }
            else
            {
                //Allow mulitple globabl accounts to be registered on the same email address...
                if (Log.IsDebugEnabled)
                {
                    Log.Debug(UserName + " IsGlobalAccount: " + UserAccountHelper.IsGlobalAccount(UserName));
                    Log.Debug(UserName + " AllowDuplicateEmailAddress: " + Data.UserAccounts.AllowDuplicateEmailAddress(createAccountParams));
                }

                //Check if user accounts respository allows the new user accounts with the same email address
                bool allowDuplicateEmailAddress = Data.UserAccounts.AllowDuplicateEmailAddress(createAccountParams);

                List<IUserAccount> allUsersWithSameEmail = Data.UserAccounts.GetAllUserAccountsByEmail(Email);
                if (!UserAccountHelper.IsGlobalAccount(UserName) && !allowDuplicateEmailAddress) //If duplicates are NOT allowed
                {
                    //Check for another account with same email address
                    if (allUsersWithSameEmail.Count == 1 && isNewUser) //Check if there is an existing account with same email
                    {
                        IUserAccount userWithSameEmail = allUsersWithSameEmail[0];
                        if (Log.IsDebugEnabled)
                            Log.Debug(UserName + " UserWithSameEmail: " + userWithSameEmail.UserName + "(" + userWithSameEmail.FirstName + " " + userWithSameEmail.LastName + ")");

                        if (userWithSameEmail.VerificationStatus == VerificationStatus.Unverified) //If the account is unverified, allow further modification of the account
                        {
                            if (Log.IsDebugEnabled)
                                Log.Debug(UserName + " Allowing modifications to existing account.");
                            //since the account has not been verified, allow the user to update details on the existing, unverified account and receive an email again.
                            account = userWithSameEmail;
                        }
                        else //Otherwise, a new account may not be created with the provided email address.
                        {
                            if (Log.IsDebugEnabled)
                                Log.Debug(UserName + " Email is already linked to an account.");
                            valid = false;
                            state.AddModelError("Email", Translations.Register_Email_Already_Linked_To_Account);
                        }
                    }
                    else
                    {
                        if (allUsersWithSameEmail.Count > 1) //If there is already more than one account registered with the current email address, then we will not allow further modification of the account regardless of verification status (harder to figure out which one to keep modifying).
                        {
                            if (Log.IsDebugEnabled)
                                Log.Debug(UserName + " Email is already linked to an account.");
                            valid = false;
                            state.AddModelError("Email", Translations.Register_Email_Already_Linked_To_Account);
                        }
                    }
                }
            }


            //Validate username
            if (UserName == "" || UserName.Length <= 2 || (!UserAccountHelper.IsGlobalAccount(UserName) && UserAccountHelper.IsWeakUsername(UserName)))
            {
                if (Log.IsDebugEnabled)
                    Log.Debug(UserName + " Invalid username.");
                valid = false;
                state.AddModelError("UserName", Translations.Register_Username_Invalid);
            }
            else
            {
                string allowedUsernameChars = "abcdefghijklmnopqrstuvwxyz0123456789_@.-";
                bool invalidChar = false;
                for (int i = 0; i < UserName.Length; i++)
                {
                    if (!allowedUsernameChars.Contains(UserName[i].ToString().ToLower()))
                    {
                        valid = false;
                        invalidChar = true;
                        state.AddModelError("UserName", Translations.Register_Username_Invalid_Character + "'" + UserName[i] + "'");
                    }
                }
                if (!invalidChar)
                {
                    //Check for other accounts with the same username
                    IUserAccount userWithSameUsername = Data.UserAccounts.GetUserAccountByUsername(UserName);
                    if (userWithSameUsername != null && userWithSameUsername.Id != userAccountId)
                    {
                        //If an existing, unverified account exists where both the username and email are the same as those entered now
                        //then we can proceed (the account variable would have been set in the email validation code above).
                        //Otherwise, display an error -- duplicate accounts are not allowed.
                        if (account == null || (account != null && !account.Id.Equals(userWithSameUsername.Id)))
                        {
                            valid = false;
                            state.AddModelError("UserName", Translations.Register_Username_Already_In_Use);
                        }
                    }
                }
            }

            //Validate Password
            if ((isNewUser || Password != null) && (Password == null || UserAccountHelper.IsWeakPassword(UserName, Password)))
            {
                valid = false;
                state.AddModelError("Password", Translations.Register_Password_Invalid);
            }

            //Validate Password
            if (Password != null && !Password.Equals(Password2))
            {
                valid = false;
                state.AddModelError("Password2", Translations.ChangePassword_Passwords_Do_Not_Match);
            }

            //Validate FirstName
            if (FirstName == null || (FirstName != null && FirstName.Length < 2))
            {
                valid = false;
                state.AddModelError("FirstName", Translations.Register_FirstName_Invalid);
            }

            //Validate LastName
            if (LastName == null || (LastName != null && LastName.Length < 2))
            {
                valid = false;
                state.AddModelError("LastName", Translations.Register_LastName_Invalid);
            }


            //Validate CultureName1
            if (string.IsNullOrEmpty(CultureName1))
            {
                valid = false;
                state.AddModelError("CultureName1", Translations.Register_CultureName_Invalid);
            }
            else
            {
                try
                {
                    CultureInfo culture = new CultureInfo(CultureName1);
                }
                catch
                {
                    valid = false;
                    state.AddModelError("CultureName1", Translations.Register_CultureName_Invalid);
                }
            }

            //Validate CultureName2
            if (string.IsNullOrEmpty(CultureName2))
            {
                valid = false;
                state.AddModelError("CultureName2", Translations.Register_CultureName_Invalid);
            }
            else
            {
                try
                {
                    CultureInfo culture = new CultureInfo(CultureName2);
                }
                catch
                {
                    valid = false;
                    state.AddModelError("CultureName2", Translations.Register_CultureName_Invalid);
                }
            }

            //Validate CultureName3
            if (string.IsNullOrEmpty(CultureName3))
            {
                //Allow Culture 3 to be blank
            }
            else
            {
                try
                {
                    CultureInfo culture = new CultureInfo(CultureName3);
                }
                catch
                {
                    valid = false;
                    state.AddModelError("CultureName3", Translations.Register_CultureName_Invalid);
                }
            }

            if (Log.IsDebugEnabled)
                Log.Debug(UserName + " Registration valid: " + valid);
            
            if (valid)
            {                
                if (account != null)
                {
                    createAccountParams.UserAccountId = account.Id;
                }

                if (isNewUser)
                {
                    EmailOrPasswordChanged = false; //doen't apply to new accounts
                    account = Data.UserAccounts.CreateUserAccount(createAccountParams);

                    //Send email
                    string subject = Translations.Register_EMail_Subject.Parameterize(account, this, Config.Settings);
                    string body = Translations.Register_Email_Template.Parameterize(account, this, Config.Settings);
                    bool emailSent = false;
                    string emailError = "";
                    try
                    {
                        emailSent = EmailHelper.SendEmailToUser(subject,body,account, account.Email);
                    }
                    catch (Exception ex)
                    {
                        emailSent = false;
                        emailError = ex.Message;
                    }
                    if (!emailSent)
                    {
                        state.AddModelError("", Translations.Register_Failed_To_Send_Email + "<br />" + emailError);
                    }
                    return emailSent;
                }
                else
                {
                    account = Data.UserAccounts.ModifyUserAccount(createAccountParams);
                    EmailOrPasswordChanged = !string.IsNullOrEmpty(Password) || !account.Email.Equals(Email, StringComparison.InvariantCultureIgnoreCase) || !account.Email.Equals(account.ProposedEmail, StringComparison.InvariantCultureIgnoreCase);
                    if (account != null && EmailOrPasswordChanged)
                    {
                        string verificationCode = account.SetProposedEmailAndPassword(Email, Password);
                        string subject = Translations.EditProfile_Verify_Email_Subject.Parameterize(account, this, Config.Settings);
                        string body = Translations.EditProfile_Verify_Email.Parameterize(account, this, Config.Settings);
                        bool emailSent = false;
                        string emailError = "";
                        try
                        {
                            string email = account.ProposedEmail;
                            if (string.IsNullOrEmpty(account.ProposedEmail))
                            {
                                email = account.Email;
                            }
                            emailSent = EmailHelper.SendEmailToUser(subject, body, account, email);
                        }
                        catch (Exception ex)
                        {
                            emailSent = false;
                            emailError = ex.Message;
                        }
                        if (!emailSent)
                        {
                            state.AddModelError("", Translations.Register_Failed_To_Send_Email + "<br />" + emailError);
                        }
                        return emailSent;
                    }
                    return account != null;
                }
            }

            return false;
        }
    }
}
